package io.github.wangtisnruipopo.carscore.types;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.IdUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <h2>游戏对局管理抽象类</h2>
 *
 * @param <S> 游戏中所使用的卡牌套装类型
 */
public abstract class AbsGame<
        D extends AbsDecorValue,
        V extends AbsCardValue,
        C extends AbsSimpleCard<D, V>,
        S extends AbsSuitCards<D, V, C>,
        P extends AbsPlayer<D, V, C>,
        R extends AbsPlayCardsRecord<D, V, ? extends AbsPlayCardsType<C>, C, P>,
        O extends AbsRound<D, V, C, P, R>
        > implements IGame<D, V, C, S, P, R, O>, IEnd {
    @Getter
    @Setter
    protected String id;
    @Getter
    protected List<P> players;
    @Getter
    protected S suitCards;
    @Getter
    @Setter
    protected boolean status;
    @Getter
    @Setter
    protected boolean end;
    @Getter
    protected List<O> rounds;
    protected Class<S> suitCardsType;
    protected Class<O> roundType;

    {
        this.create();
    }

    @Override
    @SuppressWarnings("unchecked")
    public void create() {
        // 创建游戏对局id
        this.id = IdUtil.fastSimpleUUID();
        // 初始化状态：游戏未结束
        this.end = false;
        // 初始化状态：游戏准备中，未开始
        this.status = false;
        this.players = new ArrayList<>();
        this.rounds = new ArrayList<>();
        this.suitCardsType = (Class<S>) ClassUtil.getTypeArgument(this.getClass(), 3);
        this.roundType = (Class<O>) ClassUtil.getTypeArgument(this.getClass(), 6);
        this.createSuitCards();
        // 添加玩家
        List<P> players = new ArrayList<>();
        this.initPlayers(players);
        players.forEach(this::addPlayer);
    }

    @SneakyThrows
    @Override
    public void createSuitCards() {
        this.suitCards = suitCardsType.getDeclaredConstructor().newInstance();
        // 洗牌
        this.suitCards.shuffle();
    }

    @Override
    public void start() {
        // 发牌
        this.deal();
        // 创建新的回合
        this.addRound();
    }

    @Override
    public void deal() {
        this.beforeDeal();
        // 发牌前执行逻辑
        boolean flag = true;
        // 发牌顺序
        int index = 0;
        S s = this.getSuitCards();
        while (s.hasCards() && flag) {
            int currentDealIndex = index++ % this.players.size();
            int count = 1;
            IPlayer<D, V, C> currentDealPlayer = this.players.get(currentDealIndex);
            boolean deal = currentDealPlayer.isDeal();
            while (!deal) {
                currentDealIndex = index++ % this.players.size();
                count++;
                if (count > this.players.size()) {
                    flag = false;
                    break;
                }
                currentDealPlayer = this.players.get(currentDealIndex);
                deal = currentDealPlayer.isDeal();
            }
            if (deal) {
                // 从牌堆顶部抽取一张牌
                C card = s.drawFromTop();
                currentDealPlayer.push(card);
            }
        }
        this.afterDeal();
    }

    @Override
    public void addPlayer(P player) {
        this.players.add(player);
        player.setGame(this);
    }

    @SneakyThrows
    @Override
    public void addRound() {
        O round = this.roundType.getDeclaredConstructor().newInstance();
        round.setIndex(this.rounds.size());
        // 设置本回合所有的参与者
        List<P> participantList = this.players.stream().filter(AbsPlayer::isAlive).collect(Collectors.toList());
        round.setParticipant(participantList);
        round.afterCreate();
        round.setRoundEnd(this);
        this.rounds.add(round);
    }

    @Override
    public void end() {
        // 回合结束后的回调事件
        // 回合结束后，判断下是否需要结束游戏
        // 如果不结束游戏则创建新的回合
    }

    /**
     * <h2>初始化游戏玩家</h2>
     */
    public abstract void initPlayers(List<P> players);

    /**
     * <h2>发牌前的回调函数</h2>
     */
    public abstract void beforeDeal();

    /**
     * <h2>发牌后的回调函数</h2>
     */
    public abstract void afterDeal();
}
